package com.panopset.sf;

import java.awt.Image;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JPanel;

import com.panopset.compat.Logz;
import com.panopset.compat.Strings;
import com.panopset.compat.Zombie;
import com.panopset.io.AppBase;
import com.panopset.io.AppProps;
import com.panopset.io.Filez;
import com.panopset.sf.FontManager.Size;
import com.panopset.sf.env.LogHistory;

/**
 * Panopset Swing application framework.
 * 
 * To customize for your organization, set the following System properties prior
 * to instantiating this class:
 * 
 * <table>
 * <tr>
 * <th>System property</th>
 * <th>Default</th>
 * </tr>
 * <tr>
 * <td>com.panopset.org.home</td>
 * <td>com.panopset</td>
 * </tr>
 * <tr>
 * <td>com.panopset.org.favicon</td>
 * <td>/com/panopset/site/favicon.png</td>
 * </tr>
 * </table>
 * 
 * Alternatively you may call setOrgHome and setIconResourcePath, prior to
 * launch, to accomplish the same thing.
 * 
 * @author Karl Dinwiddie
 * 
 */
public abstract class SwingFrame {

	private LogHistory logHistory;
	
	public LogHistory getLogHistory() {
		if (logHistory == null) {
			logHistory = new LogHistory();
			Logz.addListener(logHistory);
		}
		return logHistory;
	}
	/**
	 * Initial application launch.
	 */
	protected synchronized final void launch() {
		getLogHistory();
		try {
			FontManager.getInstance().setSize(
					FontManager.Size.find(Integer.parseInt(getAppProps().getFileProps().get(
							FONT_SIZE_KEY))));
		} catch (Throwable t) {
			// nothing to do.
		}
		for (String s : getAppProps().getAppBase().getDocsList()) {
			add(new SwingDoc(this, new File(s)));
		}
		if (getDocsCopy().isEmpty()) {
			add(new SwingDoc(this, findNextUntitled()));
		}
	}

	private static final String FONT_SIZE_KEY = "com.panopset.font_size";

	public synchronized void setFontSize(Size size) {
		FontManager.getInstance().setSize(size);
		for (SwingDoc sd : getDocsCopy()) {
			sd.updateFontMenuCheckboxes(size);
		}
	}

	public AppBase getAppBase() {
		return getAppProps().getAppBase();
	}

	private AppProps appProps;
	
	private AppProps getAppProps() {
		if (appProps == null) {
			appProps = new AppProps(this.getClass().getName());
		}
		return appProps;
	}

	/**
	 * @param doc
	 *            Document to create panel for.
	 * @return Application customized main JPanel.
	 */
	protected abstract JPanel createPanel(SwingDoc doc);

	/**
	 * Icon resource path.
	 */
	private String iconResourcePath;

	/**
	 * Override this method to provide your own application frame icons.
	 * 
	 * @return Icon resource path.
	 */
	public String getIconResourcePath() {
		if (iconResourcePath == null) {
			iconResourcePath = System.getProperty("com.panopset.org.favicon");
			if (!(Strings.isPopulated(iconResourcePath))) {
				iconResourcePath = DFT_ICON_PATH;
			}
		}
		return iconResourcePath;
	}

	/**
	 * 
	 * @param value
	 *            Resource path to 16x16 frame icon.
	 */
	public void setIconResourcePath(final String value) {
		iconResourcePath = value;
	}

	/**
	 * /com/panopset/site/favicon.png.
	 */
	private static final String DFT_ICON_PATH

	= "/com/panopset/site/favicon.png";

	/**
	 * 
	 * @param value
	 *            ie com.company
	 */
	public void setOrgHome(final String value) {
		getAppProps().setOrgHome(value);
	}

	public void doNew() {
		add(new SwingDoc(this, findNextUntitled()));
	}
	
	protected synchronized int getNumberOfOpenDocs() {
		return getDocsCopy().size();
	}

	private File findNextUntitled() {
		File rtn = null;
		for (int i = 0; i < 100; i++) {
			StringWriter fn = new StringWriter();
			fn.append(new Filez().combinePaths(getAppProps().getAppBase().getBaseDirectory(),
					"documents/Untitled"));
			if (i < 10) {
				fn.append("0");
			}
			fn.append("" + i);
			fn.append(".txt");
			rtn = new File(fn.toString());
			if (!isDocOpenForFile(rtn)) {
				return rtn;
			}
		}
		return rtn;
	}

	protected synchronized void add(final SwingDoc sd) {
		getDocs().add(sd);
		sd.show();
	}

	protected synchronized void saveApplication() {
		try (BufferedWriter bw = new BufferedWriter(new FileWriter(getAppProps().getAppBase()
				.getAppDocsFile()))) {
			final String eol = new Strings().getEol();
			for (String s : getAppProps().getAppBase().getDocsList()) {
				bw.write(s);
				bw.write(eol);
			}
			bw.flush();
		} catch (IOException ex) {
			throw new RuntimeException(ex);
		}
		getAppProps().getFileProps().put(FONT_SIZE_KEY,
				"" + FontManager.getInstance().getSize().getValue());
	}

	private synchronized void rememberOpenDocs() {
		getAppProps().getAppBase().getDocsList().clear();
		for (SwingDoc d : getDocsCopy()) {
			if (d.getFrame().isVisible()) {
				getAppProps().getAppBase().getDocsList().add(
						new Filez()
								.getCanonicalPath(d.getFileProps().getFile()));
			}
		}
	}

	private synchronized void closeAllDocs() {
		for (SwingDoc d : getDocsCopy()) {
			if (d.getFrame().isVisible()) {
				getAppProps().getAppBase().getDocsList().add(
						new Filez()
								.getCanonicalPath(d.getFileProps().getFile()));
				d.close();
			}
		}
	}

	protected synchronized boolean isDocOpenForFile(File file) {
		Filez fz = new Filez();
		for (SwingDoc sd : getDocsCopy()) {
			if (fz.getCanonicalPath(file).equals(
					fz.getCanonicalPath(sd.getFileProps().getFile()))) {
				return true;
			}
		}
		return false;
	}

	private List<SwingDoc> docs;
	
	private List<SwingDoc> getDocs() {
		if (docs == null) {
			docs = Collections.synchronizedList(new ArrayList<SwingDoc>());
		}
		return docs;
	}

	private synchronized List<SwingDoc> getDocsCopy() {
		List<SwingDoc> closedDocs = new ArrayList<SwingDoc>();
		for (SwingDoc sd : getDocs()) {
			if (sd.isClosed()) {
				closedDocs.add(sd);
			}
		}
		for (SwingDoc sd : closedDocs) {
			docs.remove(sd);
		}
		List<SwingDoc> rtn = new ArrayList<SwingDoc>();
		for (SwingDoc sd : docs) {
			rtn.add(sd);
		}
		return rtn;
	}

	/**
	 * Show about box.
	 */
	public void doAbout() {
		getAboutBox().show();
	}

	/**
	 * @return About box.
	 */
	public abstract PopupBox createAboutBox();

	/**
	 * About box.
	 */
	private PopupBox popupBox;

	/**
	 * @return about box.
	 */
	public final PopupBox getAboutBox() {
		if (popupBox == null) {
			popupBox = createAboutBox();
		}
		return popupBox;
	}

	private Image icon16x16;
	public Image getIcon16x16() {
		if (icon16x16 == null) {
			icon16x16 = new RezIcon(getIconResourcePath()).getImageResource()
					.getImage();
		}
		return icon16x16;
	}

	public synchronized void userCommandExit() {
		rememberOpenDocs();
		closeAllDocs();
		shutdownApplication();
	}

	public synchronized void shutdownApplication() {
		saveApplication();
		Zombie.stop();
	}
}
